#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sched.h>
// #ifdef _WIN32
// #include <Windows.h>
// #include <windows.h>
// #endif 
#if defined(TEST_TARGET_cpul)
#include <varch/command.h>
#include <varch/unitt.h>
#include <varch/cpul.h>
#else  
#include "init.h"
#include "command.h"
#include "unitt.h"
#include "kern.h"
#include "cpul.h"
#endif

/************************************************************************************/
/************************************* Unit Test ************************************/
/************************************************************************************/

// #define EXIT_TEST
extern uint64_t unitt_clock(void);

static int test_0(void)
{
    for (int i = 0; i < 100; i++)
    {
        if (0) 
        {
            
            #if defined (EXIT_TEST)
            exit(0);
            #endif 
            return UNITT_E_FAIL;
        }
    }
    
    return UNITT_E_OK;
}

static void unitt_task(void)
{
    static UNITT_TCASE rand_tests[] = {
        UNITT_TCASE(test_0),
        // UNITT_TCASE(test_1),
        // UNITT_TCASE(test_2),
    };

    static UNITT suites[] = {
        { "xxx suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock },
    };

    UNITT_EXE(suites);
}

/************************************************************************************/
/************************************* Base Test ************************************/
/************************************************************************************/

#ifdef _WIN32
uint16_t cpul_raw(uint8_t coreid)
{
    FILETIME idleTime, kernelTime, userTime;
    if (GetSystemTimes(&idleTime, &kernelTime, &userTime) == 0) {
        printf("Failed to get system times\n");
        return 1;
    }

    ULARGE_INTEGER idle, kernel, user;
    idle.LowPart = idleTime.dwLowDateTime;
    idle.HighPart = idleTime.dwHighDateTime;

    kernel.LowPart = kernelTime.dwLowDateTime;
    kernel.HighPart = kernelTime.dwHighDateTime;

    user.LowPart = userTime.dwLowDateTime;
    user.HighPart = userTime.dwHighDateTime;

    ULONGLONG totalSystemTime = kernel.QuadPart + user.QuadPart;
    ULONGLONG totalIdleTime = idle.QuadPart;

    // Calculate CPU usage percentage
    double cpuUsage = (double)(totalSystemTime - totalIdleTime) * 10000.0 / totalSystemTime;

    return (uint16_t)cpuUsage;
}

#else  
#define CORE_NUM_MAX                32
#define LINE_BUFFER_SIZE            1024

/**
* Structure for recording CPU counters in stat
*/
typedef struct {
    uint64_t user;                  /**< Time spent in user mode */
    uint64_t nice;                  /**< Time spent in low-priority user mode */
    uint64_t system;                /**< Time spent in system mode */
    uint64_t idle;                  /**< Time spent waiting for tasks */
    uint64_t iowait;                /**< Time spent waiting for I/O to complete */
    uint64_t irq;                   /**< Time spent processing interrupts */
    uint64_t softirq;               /**< Time spent processing soft interrupts */
    uint64_t steal;                 /**< Time spent by other OS in virtual environment */
    uint64_t guest;                 /**< Time spent running guest OS */
    uint64_t guest_nice;            /**< Time spent running low priority guest OS */
} cputime_t;

static uint16_t loadTable[CORE_NUM_MAX] = {0};

static cputime_t read_stat(int coreid) 
{
    cputime_t work;
    FILE *file;
    char line[LINE_BUFFER_SIZE];

    file = fopen("/proc/stat", "rb");
    if (file == NULL) {
    }

    if (fgets(line, sizeof(line), file) == NULL) {
        goto error;
    }

    if (sscanf(line, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
                &work.user,
                &work.nice,
                &work.system,
                &work.idle,
                &work.iowait,
                &work.irq,
                &work.softirq,
                &work.steal,
                &work.guest,
                &work.guest_nice) < 4) {
        goto error;
    }

    if (coreid > 0) 
    {
        int i;

        for (i = 0; i < coreid; i++) 
        {
            if (fgets(line, sizeof(line), file) == NULL) 
            {
                goto error;
            }

            if (sscanf(line, "cpu%*u %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
                    &work.user,
                    &work.nice,
                    &work.system,
                    &work.idle,
                    &work.iowait,
                    &work.irq,
                    &work.softirq,
                    &work.steal,
                    &work.guest,
                    &work.guest_nice) < 4) 
            {
                goto error;
            }
        }
    }

error:
    fclose(file);
    return work;
}

void *loadupdate_entry(void *ptr)
{
    const int coreNum = *(int *)ptr;
    cputime_t before[coreNum], after[coreNum], diff[coreNum];
    int i = 0;

    while(1)
    {
        /* Calcul real time load */
        for (i = 0; i < coreNum; i++) before[i] = read_stat(i);
        usleep(1000000);
        for (i = 0; i < coreNum; i++) after[i] = read_stat(i);

        for (i = 0; i < coreNum; i++)
        {
            if (after[i].user > before[i].user && after[i].idle > before[i].idle)
            {
                diff[i].user = after[i].user - before[i].user;
                diff[i].idle = after[i].idle - before[i].idle;
                loadTable[i] = (uint16_t)(((float)(diff[i].user) / (float)(diff[i].user + diff[i].idle)) * 10000);
            }
            else  
            {
                loadTable[i] = 0;
            }

            // printf("[core%d] load %d\r\n", i, loadTable[i]);
        }
    }
}

uint16_t cpul_raw(uint8_t coreid)
{
    if (coreid >= CORE_NUM_MAX) return 0xFFFF;
    return (uint16_t)(loadTable[coreid]);
}

void *loadgen_entry(void *ptr)
{
    #define PERIOD 10
    const int coreid = *(int *)ptr;
    CPUL cpul;
    int ret = 0;
    uint32_t count = 0;
    uint16_t load = 0;

    cpul.coreid = coreid;
    cpul.resolution = 10;
    cpul.raw = cpul_raw;
    ret = cpul_init(&cpul);

    ret = cpul_set(&cpul, 5000);

    while(1)
    {
        count += PERIOD;
        if (count >= 25200000) count = 0;

        if (count % 1000 == 0)
        {
            ret = cpul_get(&cpul, &load);
            printf("cpul_get<%d> %2d.%02d%%, %d, %d\r\n", coreid, (uint16_t)(load / 100), load % 100, cpul.ctrl.tload, cpul.ctrl.refine);
        }

        cpul_task(&cpul);
        usleep(1000 * PERIOD);
    }
}
#endif 

static void test_base(void)
{
#ifdef _WIN32
    printf("cpul %u\r\n", cpul_raw(0));
#else 
    int num_cpus = sysconf(_SC_NPROCESSORS_CONF);
    pthread_t thread_update, thread_gen[num_cpus];
    int coreid[num_cpus];
    int ret;

    printf("Number of CPUs: %d\n", num_cpus);

    ret = pthread_create(&thread_update, NULL, &loadupdate_entry, &num_cpus);
    if (ret != 0) {
        printf("pthread_create failed!\n");
    }

    for (int i = 0; i < num_cpus; i++) 
    {
        coreid[i] = i;
        ret = pthread_create(&thread_gen[i], NULL, &loadgen_entry, &coreid[i]);
        if (ret != 0) {
            printf("pthread_create failed!\n");
        }
        cpu_set_t cpuset;
        CPU_ZERO(&cpuset);
        CPU_SET(i, &cpuset);
        sched_setaffinity(thread_gen[i], sizeof(cpu_set_t), &cpuset);
    }
    
    pthread_join(thread_update, NULL);
    for (int i = 0; i < num_cpus; i++) 
    {
        pthread_join(thread_gen[i], NULL);
    }    
#endif 
}

/************************************************************************************/
/*************************************  Command  ************************************/
/************************************************************************************/

static void usage(void)
{
    printf(
"Usage: cpul [opt] [arg] ...\n"
"\n"
"options:\n"
"    -e <execute>        Specifies the function to execute, the default is the <base> test\n"
"                        <base>      Test base function\n"
"                        <ut>        Unit test\n"
"    -h                  Print help\n"
"    -v                  Print version\n"
"    -u [<period>]       Unit test period, unit ms, the default is 1000ms\n"
"\n"
    );
}

static int test(int argc, char *argv[])
{
    char *execute = NULL;
    int ut_period = 1000;

    /* reset getopt */
    command_opt_init();

    while (1)
    {
        int opt = command_getopt(argc, argv, "e:hvu::");
        if (opt == -1) break;

        switch (opt) 
        {
        case 'u' :
            if (command_optarg) ut_period = atoi(command_optarg);
            break;
        case 'e' :
            execute = command_optarg;
            break;
        case 'v' :
            printf("cpul version %d.%d.%d\r\n", CPUL_V_MAJOR, CPUL_V_MINOR, CPUL_V_PATCH);
            return 0;
        case '?':
            printf("Unknown option `%c`\r\n", command_optopt);
            return -1;
        case 'h' : 
        default:
            usage();
            return 0;
        }
    }

    if (execute)
    {
        if (!strcmp(execute, "base"))
        {
            test_base();
        }
        else if (!strcmp(execute, "ut"))
        {
            #if defined(TEST_TARGET_cpul)
            while (1)
            {
                unitt_task();
                usleep(1000 * ut_period);
            }
            #else  
            printf("create task %d\r\n", task_create(ut_period, unitt_task));
            #endif
        }
    }
    else  
    {
        test_base();
    }

    return 0;
}

/************************************************************************************/
/************************************ Test entry ************************************/
/************************************************************************************/

#if defined(TEST_TARGET_cpul)
int main(int argc, char *argv[])
{
    return test(argc, argv);
}
#else 
void test_cpul(void)
{
    command_export("cpul", test);
}
init_export_app(test_cpul);
#endif 
